//
//  KImage.m
//  Kia
//
//  Created by Andrey on 22/03/2009.
//  Copyright 2009 Karma Software. All rights reserved.
//

#import "KImage.h"
#import "KPixel.h"
#import "KHistogram.h"
#import "KCumulativeHistogram.h"
#import "K2DHistrogram.h"
#import "KImageLayer.h"
//#include <ImfCRgbaFile.h>

@implementation KImage

@synthesize width;
@synthesize height;
@synthesize indexOfSelectedLayer;
@synthesize needRenderBitmap;
@synthesize histogram;
@synthesize cumulativeHistogram;
@synthesize cieABHistogram;

- (id) init
{
	if (self = [super init])
	{
		width = height = 0;
		
		layers = [[NSMutableArray arrayWithCapacity:1] retain];
		cachedRenderBitmap = nil;		
		
		needRecomputeHistogram = YES;
		needRenderBitmap = YES;
		histogram = 
		[[[KHistogram alloc] initWithIntervalCount:256 
								   andChannelCount: IMAGE_CHANNEL_COUNT] retain];
		
		cumulativeHistogram = 
		[[[KCumulativeHistogram alloc] initWithIntervalCount:256 
											 andChannelCount: IMAGE_CHANNEL_COUNT] retain];
		
		cieABHistogram =
		[[[K2DHistrogram alloc] initWithIntervalCount:(256 * 256) andChannelCount:1] retain];
	}
	
	return self;
}

- (id) initWithWidth: (NSUInteger)aWidth andHeight: (NSUInteger)aHeight
{
	if (self = [super init])
	{
		width = aWidth;
		height = aHeight;
		
		layers = [[NSMutableArray arrayWithCapacity:1] retain];
		
		KImageLayer* backgroundLayer = 
		[[KImageLayer alloc] initWithWidth:aWidth andHeight:aHeight];
		// Background layer is retained by NSMutableArray
		[layers addObject:backgroundLayer];
		[backgroundLayer release];
		
		cachedRenderBitmap = 
		[[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL 
												pixelsWide:width 
												pixelsHigh:height
											 bitsPerSample:8
										   samplesPerPixel:3
												  hasAlpha:NO
												  isPlanar:NO
											colorSpaceName:NSCalibratedRGBColorSpace
											   bytesPerRow:0
											  bitsPerPixel:0] retain];
		
		needRecomputeHistogram = YES;
		needRenderBitmap = YES;
		histogram = 
		[[[KHistogram alloc] initWithIntervalCount:256 
								   andChannelCount: IMAGE_CHANNEL_COUNT] retain];
		
		cumulativeHistogram = 
		[[[KCumulativeHistogram alloc] initWithIntervalCount:256 
											 andChannelCount: IMAGE_CHANNEL_COUNT] retain];
		
		cieABHistogram =
		[[[K2DHistrogram alloc] initWithIntervalCount:(256 * 256) andChannelCount:1] retain];
	}
	
	return self;
}

- (void) loadImageFromFile: (NSString*)filename reallocatingPixels: (Boolean)reallocate
{
	NSString* urlPrefix = @"file://";
	NSString* urlFromFilename = [urlPrefix stringByAppendingString:filename];
	NSBitmapImageRep* fileContents = 
	[NSBitmapImageRep imageRepWithContentsOfURL: [NSURL URLWithString:urlFromFilename]];
	
	if (reallocate == YES)
	{
		width = [fileContents pixelsWide];
		height = [fileContents pixelsHigh];

		[layers removeAllObjects];
		layers = [[NSMutableArray arrayWithCapacity:1] retain];
		
		KImageLayer* backgroundLayer = 
		[[KImageLayer alloc] initWithWidth:width andHeight:height];
		backgroundLayer.name = @"Background";
		[layers addObject:backgroundLayer];
		[backgroundLayer release];
	}
	
	KImageLayer* backgroundLayer = [layers objectAtIndex:0];
	ForEachImagePixel(self)
	{
		KPixel* pixel = [[KPixel alloc] initWithNSColor:[fileContents colorAtX:x y:y]];
		pixel.owner = @"KImage::loadImageFromFile:reallocatingPixels";
		[backgroundLayer setPixel:pixel atX:x y:y];
		[pixel release];
	}
}

- (id) loadImageFromOpenExrFile: (NSString*)filename
{
/*	
	int minX, minY, maxX, maxY;
	ImfRgba* pixels;
	ImfInputFile* inputFile = ImfOpenInputFile([filename UTF8String]);
	const ImfHeader* fileHeader = ImfInputHeader(inputFile);
	
	// Read data window dimensions 
	ImfHeaderDataWindow(fileHeader, &minX, &minY, &maxX, &maxY);
	height = maxY - minY + 1;
	width = maxX - minX + 1;
	
	// Allocate framebuffer 
	pixels = (ImfRgba*)malloc(width * height * sizeof(ImfRgba));
	
	// Set framebuffer and read pixels
	ImfInputSetFrameBuffer(inputFile, pixels - minX - width * minY, 1, width);
	ImfInputReadPixels(inputFile, minY, maxY);
	
	[layers removeAllObjects];
	layers = [[NSMutableArray arrayWithCapacity:1] retain];
	
	KImageLayer* backgroundLayer = 
	[[KImageLayer alloc] initWithWidth:width andHeight:height];
	backgroundLayer.name = @"Background";
	[layers addObject:backgroundLayer];
	[backgroundLayer release];
	
	backgroundLayer = [layers objectAtIndex:0];
	ForEachImagePixel(self)
	{
		int index = y * width + x;
		CGFloat pixelData[4];
		
		ImfHalfToFloatArray(4, 
							(ImfHalf*)(&pixels[index]), 
							(CGFloat*)(pixelData));
		
		KPixel* pixel = [[KPixel alloc] initWithRed:pixelData[RedImageChannel] green:pixelData[GreenImageChannel] andBlue:pixelData[BlueImageChannel]];
		pixel.owner = @"KImage::loadImageFromOpenExrFile";
		[backgroundLayer setPixel:pixel atX:x y:y];
		[pixel release];		
	}
	
	free(pixels);
	ImfCloseInputFile(inputFile);
*/	
	return self;
}

- (id) initWithFile: (NSString*)filename
{
	if (self = [super init])
	{
		if ([[filename pathExtension] isEqualToString:@"exr"])
			[self loadImageFromOpenExrFile:filename];
		else
			[self loadImageFromFile:filename reallocatingPixels: YES];
		
		cachedRenderBitmap = 
		[[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL 
												pixelsWide:width 
												pixelsHigh:height
											 bitsPerSample:8
										   samplesPerPixel:3
												  hasAlpha:NO
												  isPlanar:NO
											colorSpaceName:NSCalibratedRGBColorSpace
											   bytesPerRow:0
											  bitsPerPixel:0] retain];
		
		needRecomputeHistogram = YES;
		needRenderBitmap = YES;
		histogram = [[[KHistogram alloc] initWithIntervalCount:256 
											   andChannelCount: IMAGE_CHANNEL_COUNT] retain];
		cumulativeHistogram = 
		[[[KCumulativeHistogram alloc] initWithIntervalCount:256 
											 andChannelCount: IMAGE_CHANNEL_COUNT] retain];

		cieABHistogram =
		[[[K2DHistrogram alloc] initWithIntervalCount:(256 * 256) andChannelCount:1] retain];
		
		[histogram computeForImage:self];
		[cumulativeHistogram computeForImage:self];
		[cieABHistogram computeForImage:self];
	}
	
	return self;
}

- (void) addLayerOnTop: (KImageLayer*)newLayer
{
	if (newLayer != nil)
		[layers addObject:newLayer];
	
	needRenderBitmap = YES;
}

- (void) insertLayer: (KImageLayer*)newLayer atIndex: (NSUInteger)index
{
	if (newLayer != nil)
		[layers insertObject:newLayer atIndex:index];
	
	needRenderBitmap = YES;
}

- (KImageLayer*) layerAtIndex: (NSUInteger)index
{
	return [layers objectAtIndex:index];
}

- (NSUInteger) layerCount
{
	return [layers count];
}

- (void) removeLayerFromTop
{
	[layers removeLastObject];
	
	needRenderBitmap = YES;
}

- (void) removeLayerAtIndex: (NSUInteger)index
{
	[layers removeObjectAtIndex:index];
	
	needRenderBitmap = YES;
}

- (KPixel*) pixelAtX: (NSUInteger)x y: (NSUInteger)y
{
	KImageLayer* firstNormalLayer; 
	for (int i = [layers count] - 1; i >= 0; i--)
	{
		firstNormalLayer = [layers objectAtIndex:i];
		
		if (firstNormalLayer.type == NormalLayer)
			break;
	}
	
	return [firstNormalLayer pixelAtX:x y:y];
}

- (KPixel*) pixelForRenderAtX: (NSUInteger)x y: (NSUInteger)y
{
	if ([layers count] == 1)
		return [[layers objectAtIndex:0] pixelAtX:x y:y];
	
	KPixel* resultingPixel = nil;
	for (int i = [layers count] - 1; i >= 0; i--)
	{
		KImageLayer* currentLayer = [layers objectAtIndex:i];
		
		if (currentLayer.visible == YES)
		{
			if (resultingPixel == nil)
			{
				resultingPixel = [[currentLayer pixelAtX:x y:y] copy];
				resultingPixel.owner = @"KImage::pixelForRenderAtX:y";
			}
			
			if (currentLayer.opacity == 1.0 && currentLayer.blendMode == NormalBlendMode)
			//	return [resultingPixel autorelease];
				return resultingPixel;
			
			KPixel* underlyingPixel = 
			[[layers objectAtIndex:i - 1] pixelAtX:x y:y];
			
			[resultingPixel linearBlendWithFraction:currentLayer.opacity 
											ofPixel:underlyingPixel 
									 usingBlendMode:currentLayer.blendMode];
		}
	}
	
//	return [resultingPixel autorelease];
	return resultingPixel;
}

- (void) setPixel: (KPixel*)pixel atX: (NSUInteger)x y: (NSUInteger)y
{
	KImageLayer* selectedLayer = [layers objectAtIndex:indexOfSelectedLayer];
	
	pixel.owner = @"KImage::setPixel:atX:y";
	[selectedLayer setPixel:pixel atX:x y:y];
	
	needRenderBitmap = YES;
}

- (NSBitmapImageRep*) bitmap
{
	if (needRenderBitmap == YES)
	{
		[self renderToBitmap];
		needRenderBitmap = NO;
	}

	return cachedRenderBitmap;
}

- (void) renderToBitmap
{
	ForEachImagePixel(self)
	{
		NSAutoreleasePool* subPool = [[NSAutoreleasePool alloc] init];
		KPixel* pixelForRender = [self pixelForRenderAtX:x y:y];
		NSColor* currentPixelColor = [pixelForRender nsColor];
		[cachedRenderBitmap setColor:currentPixelColor atX:x y:y];
		
		// We do not release, if pixelForRender is direct background layer pixel
		if ([layers count] != 1)
			[pixelForRender release];
		[subPool release];
	}
}

- (CGFloat) contrast
{
	return histogram.channelHistograms[BrightnessImageChannel].variance;
}

- (CGFloat) uniformity
{
	return histogram.channelHistograms[BrightnessImageChannel].uniformity;
}

- (CGFloat) colorContrast
{
	return histogram.colorContrast;
}

- (void) saveToTIFF: (NSString*)filename
{
	if (needRenderBitmap == YES)
	{
		[self renderToBitmap];
		needRenderBitmap = NO;
	}
	
	NSString* filenameWithExtension = [filename stringByAppendingString:@".tiff"];
	
	[[cachedRenderBitmap TIFFRepresentation] writeToFile:filenameWithExtension atomically:NO];
}

- (void) deallocInternal
{
	[cumulativeHistogram release];
	[histogram release];
	[cachedRenderBitmap release];
	[layers removeAllObjects];
}

- (void) restoreFromBackup: (NSString*)backupFilename
{
	[self loadImageFromFile:backupFilename reallocatingPixels:NO];
	needRenderBitmap = YES;
}

- (void) recomputeStatistics
{
	[histogram computeForImage:self];
	[cumulativeHistogram computeForImage:self];
	[cieABHistogram computeForImage:self];
}

- (void) syncWithPixels
{
	[self renderToBitmap];
	[self recomputeStatistics];
}

- (NSString*) description
{
	return [NSString stringWithFormat:@"{%.4f, %.4f, %.4f}", [self contrast], [self uniformity], [self colorContrast]];;
}

- (void) dealloc
{
	[self deallocInternal];
	[super dealloc];
}
@end
